// Copyright 2021 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "dawn/dawn_proc.h"
#include "dawn_native/DawnNative.h"
#include "tests/MockCallback.h"
#include "utils/SystemUtils.h"
#include "utils/WGPUHelpers.h"

#include <gtest/gtest.h>

namespace {

    using namespace testing;

    class DeviceCreationTest : public testing::Test {
      protected:
        void SetUp() override {
            dawnProcSetProcs(&dawn::native::GetProcs());

            instance = std::make_unique<dawn::native::Instance>();
            instance->DiscoverDefaultAdapters();
            for (dawn::native::Adapter& nativeAdapter : instance->GetAdapters()) {
                wgpu::AdapterProperties properties;
                nativeAdapter.GetProperties(&properties);

                if (properties.backendType == wgpu::BackendType::Null) {
                    adapter = wgpu::Adapter(nativeAdapter.Get());
                    break;
                }
            }
            ASSERT_NE(adapter, nullptr);
        }

        void TearDown() override {
            adapter = nullptr;
            instance = nullptr;
            dawnProcSetProcs(nullptr);
        }

        std::unique_ptr<dawn::native::Instance> instance;
        wgpu::Adapter adapter;
    };

    // Test successful call to CreateDevice with no descriptor
    TEST_F(DeviceCreationTest, CreateDeviceNoDescriptorSuccess) {
        wgpu::Device device = adapter.CreateDevice();
        EXPECT_NE(device, nullptr);
    }

    // Test successful call to CreateDevice with descriptor.
    TEST_F(DeviceCreationTest, CreateDeviceSuccess) {
        wgpu::DeviceDescriptor desc = {};
        wgpu::Device device = adapter.CreateDevice(&desc);
        EXPECT_NE(device, nullptr);
    }

    // Test successful call to CreateDevice with toggle descriptor.
    TEST_F(DeviceCreationTest, CreateDeviceWithTogglesSuccess) {
        wgpu::DeviceDescriptor desc = {};
        wgpu::DawnTogglesDeviceDescriptor togglesDesc = {};
        desc.nextInChain = &togglesDesc;

        const char* toggle = "skip_validation";
        togglesDesc.forceEnabledToggles = &toggle;
        togglesDesc.forceEnabledTogglesCount = 1;

        wgpu::Device device = adapter.CreateDevice(&desc);
        EXPECT_NE(device, nullptr);

        auto toggles = dawn::native::GetTogglesUsed(device.Get());
        EXPECT_THAT(toggles, testing::Contains(testing::StrEq(toggle)));
    }

    // Test successful call to RequestDevice with descriptor
    TEST_F(DeviceCreationTest, RequestDeviceSuccess) {
        WGPUDevice cDevice;
        {
            MockCallback<WGPURequestDeviceCallback> cb;
            EXPECT_CALL(cb, Call(WGPURequestDeviceStatus_Success, NotNull(), nullptr, this))
                .WillOnce(SaveArg<1>(&cDevice));

            wgpu::DeviceDescriptor desc = {};
            adapter.RequestDevice(&desc, cb.Callback(), cb.MakeUserdata(this));
        }

        wgpu::Device device = wgpu::Device::Acquire(cDevice);
        EXPECT_NE(device, nullptr);
    }

    // Test successful call to RequestDevice with a null descriptor
    TEST_F(DeviceCreationTest, RequestDeviceNullDescriptorSuccess) {
        WGPUDevice cDevice;
        {
            MockCallback<WGPURequestDeviceCallback> cb;
            EXPECT_CALL(cb, Call(WGPURequestDeviceStatus_Success, NotNull(), nullptr, this))
                .WillOnce(SaveArg<1>(&cDevice));

            adapter.RequestDevice(nullptr, cb.Callback(), cb.MakeUserdata(this));
        }

        wgpu::Device device = wgpu::Device::Acquire(cDevice);
        EXPECT_NE(device, nullptr);
    }

    // Test failing call to RequestDevice with invalid feature
    TEST_F(DeviceCreationTest, RequestDeviceFailure) {
        MockCallback<WGPURequestDeviceCallback> cb;
        EXPECT_CALL(cb, Call(WGPURequestDeviceStatus_Error, nullptr, NotNull(), this)).Times(1);

        wgpu::DeviceDescriptor desc = {};
        wgpu::FeatureName invalidFeature = static_cast<wgpu::FeatureName>(WGPUFeatureName_Force32);
        desc.requiredFeatures = &invalidFeature;
        desc.requiredFeaturesCount = 1;

        adapter.RequestDevice(&desc, cb.Callback(), cb.MakeUserdata(this));
    }

}  // anonymous namespace
